#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/genhd.h>
#include <asm/io.h>

#define MAJOR_NR HD_MAJOR
#include "blk.h"

static inline unsigned char CMOS_READ(unsigned char addr) {
    outb_p(0x80 | addr, 0x70);
    return inb_p(0x71);
}

#define MAX_HD 2

struct hd_i_struct {
    unsigned int head, sect, cyl, wpcom, lzone, ctl;
};
struct hd_i_struct hd_info[] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}};
static int NR_HD = 0;

static struct hd_struct hd[MAX_HD << 6] = {
    {0, 0},
};

void unexpected_hd_interrupt(void) {
    sti();
    printk("Unexpected HD interrupt\n");
}

static void hd_geninit(void);

static struct gendisk hd_gendisk = {
    MAJOR_NR,   /* Major number */
    "hd",       /* Major name */
    hd_geninit, /* init function */
    0,          /* number */
    NULL};      /* next */

extern void hd_interrupt(void);

static void hd_geninit(void) {
    int drive, i;
    extern struct drive_info drive_info;
    unsigned char *BIOS = (unsigned char *)&drive_info;
    int cmos_disks;

    if (!NR_HD) {
        for (drive = 0; drive < 2; drive++) {
            hd_info[drive].cyl = *(unsigned short *)BIOS;
            hd_info[drive].head = *(2 + BIOS);
            hd_info[drive].wpcom = *(unsigned short *)(5 + BIOS);
            hd_info[drive].ctl = *(8 + BIOS);
            hd_info[drive].lzone = *(unsigned short *)(12 + BIOS);
            hd_info[drive].sect = *(14 + BIOS);
            BIOS += 16;
        }

        cmos_disks = CMOS_READ(0x12);
        if (cmos_disks & 0xf0) {
            if (cmos_disks & 0x0f)
                NR_HD = 2;
            else
                NR_HD = 1;
        }
    }
    i = NR_HD;
    while (i-- > 0) {
        hd[i << 6].nr_sects = 0;
        if (hd_info[i].head > 16) {
            printk("hd.c: ST-506 interface disk with more than 16 heads detected,\n");
            printk("  probably due to non-standard sector translation. Giving up.\n");
            printk("  (disk %d: cyl=%d, sect=%d, head=%d)\n", i,
                   hd_info[i].cyl,
                   hd_info[i].sect,
                   hd_info[i].head);
            if (i + 1 == NR_HD)
                NR_HD--;
            continue;
        }
        hd[i << 6].nr_sects = hd_info[i].head *
                              hd_info[i].sect * hd_info[i].cyl;
    }
    if (NR_HD) {
        set_intr_gate(0x2E, &hd_interrupt);
        outb_p(inb_p(0x21) & 0xfb, 0x21);
        outb(inb_p(0xA1) & 0xbf, 0xA1);
    }
    hd_gendisk.nr_real = NR_HD;
}

void hd_init() {
    hd_gendisk.next = gendisk_head;
    gendisk_head = &hd_gendisk;
}
